#!/usr/bin/php
<?php
/*	Project:        Brutis
	Version:        0.93
	Author:         Zach Younker
	Copyright:

		Software License Agreement (BSD License)

		Copyright (c) 2009, Gear Six, Inc.
		All rights reserved.

		Redistribution and use in source and binary forms, with or without
		modification, are permitted provided that the following conditions are
		met:

		* Redistributions of source code must retain the above copyright
		  notice, this list of conditions and the following disclaimer.

		* Redistributions in binary form must reproduce the above
		  copyright notice, this list of conditions and the following disclaimer
		  in the documentation and/or other materials provided with the
		  distribution.

		* Neither the name of Gear Six, Inc. nor the names of its
		  contributors may be used to endorse or promote products derived from
		  this software without specific prior written permission.

		THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
		"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
		LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
		A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
		OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
		SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
		LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
		DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
		THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
		(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
		OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

/* Set error reporting to all and memory limit to 32M */
ini_set('memory_limit', '32M');
error_reporting(E_ALL);

require_once('lib/functions.php');

define('LARGE_FIELD_SIZE', 12);
define('MID_FIELD_SIZE', 8);
define('SMALL_FIELD_SIZE', 4);
define('LEFT', 0);
define('RIGHT', 1);

function start_server($address, $port) {
	global $settings;
	global $hosts;

	$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
	socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1);
	socket_bind($sock, $address, $port); 
	socket_listen($sock);

	$clients = array($sock);
	$clients_fp = array();

	onStart();
	while (true) {
		$read = $clients;
		if (socket_select($read, $write = NULL, $except = NULL, 1) === FALSE) {
			echo "socket_select() failed, reason: " .socket_strerror(socket_last_error()) . "\n";
			continue;
		}
		if (in_array($sock, $read)) {
			/* New connection */
			$clients[] = $newsock = socket_accept($sock);
			$key = array_search($sock, $read); 
			onConnect($newsock);
			unset($read[$key]);
		} 

		foreach ($read as $read_sock) {
			$buf = '';
			$data = '';
			while ($buf != "\n") {
				$buf = socket_read($read_sock, 1); 
				$data .= $buf;
				if ($buf === '') {
					$key = array_search($read_sock, $clients);
					unset($clients[$key]);
					onClose($read_sock);
					break;
				}
			}
			if ($data != "") {
				$data = trim($data);
				onReceiveData($read_sock, $data);
			} 
		}
	}       
	onShutdown();
	socket_close($sock);
}       




function pad ($value, $length=LARGE_FIELD_SIZE, $side=LEFT) {
	if (strlen($value) > $length) {
		$value = substr($value, 0, (((strlen($value) - $length) + 2) * -1));
		$value = $value . '..';
	} else {
		while (strlen($value) < $length) {
			if ($side == LEFT) {
				$value = ' ' . $value;
			} else {
				$value = $value . ' ';
			}
		}
	}
	return $value;
}

function clear_screen() {
	$codes =  array(27, 91, 72, 27, 91, 50, 74);
	foreach ($codes as $code) {
		print(chr($code));
	}
 }

	
function summary() {
	global $totals;

	$sample = array();
	$sample['key_count'] = 0;
	$sample['set_count'] = 0;
	$sample['get_count'] = 0;
	$sample['hit_count'] = 0;
	$sample['miss_count'] = 0;
	$sample['set_fail_count'] = 0;
	$sample['md5_fail_count'] = 0;
	$sample['set_transferred'] = 0;
	$sample['get_transferred'] = 0;
	$sample['operations'] = 0;
	$sample['set_latency'] = 0;
	$sample['get_latency'] = 0;
	$sample['dataset_size'] = 0;

	foreach ($totals as $host=>$total) {
		foreach ($total['raw'] as $key=>$value) {
			$sample[$key] = $sample[$key] + $value;
		}
                $sample['dataset_size'] = $sample['dataset_size'] + ($sample['key_count'] * $totals[$host]['settings']['object_size']);
	}

	$summary =  ("\n\nTotal Operations:\t" . pad($sample['operations']) . "\n" .
			"Total Dataset Size:\t" . pad($sample['dataset_size']) . "\n" .
			"Total Sets:\t\t" . pad($sample['set_count']) . "\n" .
			"Total Gets:\t\t" . pad($sample['get_count']) . "\n" .
			"Total Hits:\t\t" . pad($sample['hit_count']) . "\n" .
			"Total Misses:\t\t" . pad($sample['miss_count']) . "\n" .
			"Total Set Fails:\t" . pad($sample['set_fail_count']) . "\n" .
			"Total MD5 Fails:\t" . pad($sample['md5_fail_count']) . "\n" .
			"Total Keys:\t\t" . pad($sample['key_count']) . "\n" .
			"Total Transferred:\t" . pad($sample['set_transferred'] + $sample['get_transferred']) . "\n" .
			"\n");
	return $summary;
}

function output_data($elapsed_time) {
/*	output_data()
	output stats to screen/disk
	@param $elapsed_time int
*/
	global $connections;
	global $settings;
	global $totals;
	global $foutput;
	global $clients;
	global $last_write;

	$sample = array();
	$sample['timestamp'] = $last_write;
	$sample['connections'] = $connections;
	$sample['clients'] = $clients;
	$sample['key_count'] = 0;
	$sample['set_count'] = 0;
	$sample['get_count'] = 0;
	$sample['hit_count'] = 0;
	$sample['miss_count'] = 0;
	$sample['set_fail_count'] = 0;
	$sample['md5_fail_count'] = 0;
	$sample['set_transferred'] = 0;
	$sample['get_transferred'] = 0;
	$sample['operations'] = 0;
	$sample['set_latency'] = 0;
	$sample['get_latency'] = 0;

	if ($settings['verbose']) {
		foreach ($totals as $host=>$total) {
			foreach ($total['sample'] as $key=>$value) {
				$sample[$key] = $sample[$key] + $value;
			}
		}
		$sps = number_format($sample['set_count'] / $elapsed_time, 2, '.', ',');
		$gps = number_format($sample['get_count'] / $elapsed_time, 2, '.', ',');
		$hps = number_format($sample['hit_count'] / $elapsed_time, 2, '.', ',');
		$mps = number_format($sample['miss_count'] / $elapsed_time, 2, '.', ',');
		$sfps = number_format($sample['set_fail_count'] / $elapsed_time, 2, '.', ',');
		$mfps = number_format($sample['md5_fail_count'] / $elapsed_time, 2, '.', ',');
		$tfps = number_format(($sample['md5_fail_count'] + $sample['set_fail_count']) / $elapsed_time, 2, '.', ',');
		$tmbps = number_format(((($sample['set_transferred'] + $sample['get_transferred']) / 1024 / 1024) / $elapsed_time), 2, '.', ',');
		$smbps = number_format((($sample['set_transferred'] / 1024 / 1024) / $elapsed_time), 2, '.', ',');
		$gmbps = number_format((($sample['get_transferred'] / 1024 / 1024) / $elapsed_time), 2, '.', ',');
		$ops = number_format(($sample['operations'] / $elapsed_time), 2, '.', ',');

		if (($sample['set_latency'] != 0) && ($sample['set_count'] != 0)) {
			$sample['set_latency'] = number_format(($sample['set_latency'] / $sample['set_count']), 6, '.', ',');
		} else {
			$sample['set_latency'] = number_format(0, 6, '.', ',');
		}
		if (($sample['get_latency'] != 0) && ($sample['get_count'] != 0)) {
			$sample['get_latency'] = number_format(($sample['get_latency'] / $sample['get_count']), 6, '.', ',');
		} else {
			$sample['get_latency'] = number_format(0, 6, '.', ',');
		}
		if (($sample['set_latency'] != 0) && ($sample['get_latency'] != 0)) {
			$total_latency = number_format((($sample['set_latency'] + $sample['get_latency']) / 2), 6, '.', ',');
		} else {
			$total_latency = number_format(0, 6, '.', ',');
		}

		clear_screen();

		print  ("Brutis                                                 " . pad(number_format($connections, 0)) ." Connections\n\n" .
			'Type   ' . pad('Ops/sec') . pad('Hits/sec') . pad('Misses/sec') . pad('Fails/sec') . pad('Latency') . pad('MB/sec') . "\n" .
			"-------------------------------------------------------------------------------\n" .
			'Sets:  ' . pad($sps) . pad('---') . pad('---') . pad($sfps) . pad($sample['set_latency']) . pad($smbps) . "\n" .
			'Gets:  ' . pad($gps) . pad($hps) . pad($mps) . pad($mfps) . pad($sample['get_latency']) . pad($gmbps) . "\n" .
			'Totals:' . pad($ops) . pad($hps) . pad($mps) . pad($tfps) . pad($total_latency) . pad($tmbps) . "\n" .
			"-------------------------------------------------------------------------------\n\n" .

			"                              SETS                             GETS\n" .
			"-------------------------------------------------------------------------------\n" .
			"Host        " . pad('Ops/sec') . pad('Latency') . pad('MB/sec', MID_FIELD_SIZE) . " " . pad('Ops/sec') . pad('Latency') . pad('MB/sec', MID_FIELD_SIZE) . "\n" .
			"-------------------------------------------------------------------------------\n");

		foreach ($totals as $host=>$total) {
			if ($total['disconnected'] != TRUE) {
				if (isset($total['settings'])) {
					if (($total['sample']['set_latency'] != 0) && ($total['sample']['set_count'] != 0)) {
						$c_set_latency = number_format(($total['sample']['set_latency'] / $total['sample']['set_count']), 6, '.', ',');
					} else {
						$c_set_latency = number_format(0, 6, '.', ',');
					}
					if (($total['sample']['get_latency'] != 0) && ($total['sample']['get_count'] != 0)) {
						$c_get_latency = number_format(($total['sample']['get_latency'] / $total['sample']['get_count']), 6, '.', ',');
					} else {
						$c_get_latency = number_format(0, 6, '.', ',');
					}
	
					$c_sps = number_format($total['sample']['set_count'] / $elapsed_time, 2, '.', ',');
					$c_gps = number_format($total['sample']['get_count'] / $elapsed_time, 2, '.', ',');
					$c_smbps = number_format((($total['sample']['set_transferred'] / 1024 / 1024) / $elapsed_time), 2, '.', ',');
					$c_gmbps = number_format((($total['sample']['get_transferred'] / 1024 / 1024) / $elapsed_time), 2, '.', ',');
					$c_ratio = $total['settings']['set_ratio'] . ':' . $total['settings']['get_ratio'];
					$c_pattern = $total['settings']['set_pattern'] . ':' . $total['settings']['get_pattern'];
					if ($total['settings']['checksum'] == TRUE) {
						$c_checksum = "TRUE";
					} else {
						$c_checksum = "FALSE";
					}
		
					printf(pad($host, LARGE_FIELD_SIZE, RIGHT) . pad($c_sps) . pad($c_set_latency) . pad($c_smbps, MID_FIELD_SIZE) . " " . pad($c_gps) . pad($c_get_latency) . pad($c_gmbps, MID_FIELD_SIZE) . "\n");
							
				}
			}
		}
	}

	if ($settings['filename'] != NULL) {
		write_sample($sample);
	}
}

function write_sample($data) {
	global $foutput;
	global $settings;

	switch ($settings['output_format']) {
		case 'json':
			$json = new Services_JSON();
			$foutput = fopen($settings['filename'], "a+");
			fwrite($foutput, ','.$json->encodeUnsafe($data));
			fclose($foutput);
		break;
		case 'csv':
			if (!$foutput) {
				$output = '#';
				foreach ($data as $key=>$value) {
					$output .= $key.',';
				}
				$output = substr($output, 0, (strlen($output) - 1));
	
				$foutput = fopen($settings['filename'], 'w');
				fwrite($foutput, $output."\n");
			} else {
				$output = '';
				foreach ($data as $key=>$value) {
					$output .= "$value,";
				}
				$output = substr($output, 0, (strlen($output) - 1));
				fwrite($foutput, $output."\n");
			}
		break;
	}
		
}
		
function write_total($data) {
	global $foutput;
	global $settings;

	switch ($settings['output_format']) {
		case 'json':
			$json = new Services_JSON();
			$foutput = fopen($settings['filename'], 'a+');
			fwrite($foutput, ','.$json->encodeUnsafe($data));
			fclose($foutput);
		break;
		case 'csv':
			fwrite($foutput, summary());
		break;
	}
}

function onStart() {
/*	onStart()
	initialize variables, open output file  on startup
*/
	global $totals;
	global $foutput;
	global $settings;
	global $in_service;
	global $last_write;

	$in_service = TRUE;
	$last_write = microtime(TRUE);

	reset_samples();

	/* if filename != NULL then we need to output results to file */
	if ($settings['filename'] != NULL) {
		if ($settings['output_format'] == 'json') {
			$foutput = fopen($settings['filename'], 'w');
			fwrite($foutput, "{\"stats\":[{\"collector\":\"la-cn1:9091\"}");
			fclose($foutput);
		}
	}
}

function onShutdown() {
/*	onShutdown()
	All clients disconnected, cleanup and exit
*/
	global $totals;
	global $foutput;
	global $settings;
	global $in_service;
	global $last_write;

	if ($in_service == TRUE) {
		$in_service = FALSE;
		$current_time = microtime(TRUE);
		$elapsed_time = $current_time - $last_write;
		$last_write = $current_time;
		output_data($elapsed_time);

		if ($settings['verbose']) {
			print(summary());
		}

		if ($settings['filename'] != NULL) {
			switch ($settings['output_format']) {
				case 'json':
					write_total($totals);
					$foutput = fopen($settings['filename'], 'a+');
					fwrite($foutput, ']}');
				break;
				case 'csv':
					fwrite($foutput, "END\n");
					write_total($totals);
					fclose($foutput);
				break;
			}
		}
	}
	exit(0);
}
	
function new_host ($host) {
/* new_host()
	add new host to totals
	@param $host string
*/
	global $totals;

	$totals[$host] = array();
	$totals[$host]['disconnected'] = FALSE;
	$totals[$host]['raw'] = array();
	$totals[$host]['raw']['key_count'] = 0;
	$totals[$host]['raw']['operations'] = 0; 
	$totals[$host]['raw']['set_count'] = 0;
	$totals[$host]['raw']['get_count'] = 0;
	$totals[$host]['raw']['hit_count'] = 0;
	$totals[$host]['raw']['miss_count'] = 0;
	$totals[$host]['raw']['set_fail_count'] = 0;
	$totals[$host]['raw']['md5_fail_count'] = 0;
	$totals[$host]['raw']['set_transferred'] = 0;
	$totals[$host]['raw']['get_transferred'] = 0;

	$totals[$host]['sample'] = array();
	$totals[$host]['sample']['key_count'] = 0;
	$totals[$host]['sample']['operations'] = 0; 
	$totals[$host]['sample']['set_count'] = 0;
	$totals[$host]['sample']['get_count'] = 0;
	$totals[$host]['sample']['hit_count'] = 0;
	$totals[$host]['sample']['miss_count'] = 0;
	$totals[$host]['sample']['set_fail_count'] = 0;
	$totals[$host]['sample']['md5_fail_count'] = 0;
	$totals[$host]['sample']['set_transferred'] = 0;
	$totals[$host]['sample']['get_transferred'] = 0;
	$totals[$host]['sample']['set_latency'] = 0;
	$totals[$host]['sample']['get_latency'] = 0;

}

function OnConnect ($client_id = 0) {
/*	onConnect() {
	new client connect
*/
	global $clients;

	$clients++;

}

function OnClose($client_id = 0) {
/*	onClose()
	Client disconnected
*/
	global $clients;
	global $totals;
	global $hosts;
	global $last_write;

	$clients--;

	if (isset($hosts[$client_id])) {
		$host = $hosts[$client_id];
		unset($hosts[$client_id]);

		$remove = TRUE;
		foreach ($hosts as $key=>$value) {
			if ($host == $value) {
				$remove = FALSE;
			}
		}

		if ($remove == TRUE) {
			$totals[$host]['disconnected'] = TRUE;
		}
	}
	if ($clients == 0) {
		onShutdown();
	}
}

function onReceiveData ($client_id = 0, $data = '') {
/*	onReceiveData()
	New incoming data from client
*/
	global $connections;
	global $totals;
	global $settings;
	global $last_write;
	global $hosts;

	$buf = null;

	if (!$data = unserialize($data)) {
		print ("Error in collector data\n");
		$buf = "err\n";
		socket_write($client_id, $buf, strlen($buf));
	} else {
		$buf = "ack\n";
		socket_write($client_id, $buf, strlen($buf));
	}

	if (!isset($hosts[$client_id])) {
		$hosts[$client_id] = $data['settings']['hostname'];
		new_host($data['settings']['hostname']);
	}

	$host = $hosts[$client_id];

	if (isset($data['key_count'])) {
		$totals[$host]['sample']['key_count'] = $data['key_count'];		
		$totals[$host]['raw']['key_count'] =  $data['key_count'];		
	}
	if (isset($data['operations'])) {
		$totals[$host]['sample']['operations'] = $totals[$host]['sample']['operations'] + $data['operations'];		
		$totals[$host]['raw']['operations'] = $totals[$host]['raw']['operations'] + $data['operations'];		
	}
	if (isset($data['set_count'])) {
		$totals[$host]['sample']['set_count'] = $totals[$host]['sample']['set_count'] + $data['set_count'];		
		$totals[$host]['raw']['set_count'] = $totals[$host]['raw']['set_count'] + $data['set_count'];		
	}
	if (isset($data['hit_count'])) {
		$totals[$host]['sample']['hit_count'] = $totals[$host]['sample']['hit_count'] + $data['hit_count'];		
		$totals[$host]['raw']['hit_count'] = $totals[$host]['raw']['hit_count'] + $data['hit_count'];		
	}
	if (isset($data['get_count'])) {
		$totals[$host]['sample']['get_count'] = $totals[$host]['sample']['get_count'] + $data['get_count'];		
		$totals[$host]['raw']['get_count'] = $totals[$host]['raw']['get_count'] + $data['get_count'];		
	}
	if (isset($data['miss_count'])) {
		$totals[$host]['sample']['miss_count'] = $totals[$host]['sample']['miss_count'] + $data['miss_count'];		
		$totals[$host]['raw']['miss_count'] = $totals[$host]['raw']['miss_count'] + $data['miss_count'];		
	}
	if (isset($data['set_fail_count'])) {
		$totals[$host]['sample']['set_fail_count'] = $totals[$host]['sample']['set_fail_count'] + $data['set_fail_count'];		
		$totals[$host]['raw']['set_fail_count'] = $totals[$host]['raw']['set_fail_count'] + $data['set_fail_count'];		
	}
	if (isset($data['md5_fail_count'])) {
		$totals[$host]['sample']['md5_fail_count'] = $totals[$host]['sample']['md5_fail_count'] + $data['md5_fail_count'];		
		$totals[$host]['raw']['md5_fail_count'] = $totals[$host]['raw']['md5_fail_count'] + $data['md5_fail_count'];		
	}
	if (isset($data['set_transferred'])) {
		$totals[$host]['sample']['set_transferred'] = $totals[$host]['sample']['set_transferred'] + $data['set_transferred'];		
		$totals[$host]['raw']['set_transferred'] = $totals[$host]['raw']['set_transferred'] + $data['set_transferred'];		
	}
	if (isset($data['get_transferred'])) {
		$totals[$host]['sample']['get_transferred'] = $totals[$host]['sample']['get_transferred'] + $data['get_transferred'];		
		$totals[$host]['raw']['get_transferred'] = $totals[$host]['raw']['get_transferred'] + $data['get_transferred'];		
	}
	if (isset($data['set_latency'])) {
		$totals[$host]['sample']['set_latency'] = $totals[$host]['sample']['set_latency'] + $data['set_latency'];		
	}
	if (isset($data['get_latency'])) {
		$totals[$host]['sample']['get_latency'] = $totals[$host]['sample']['get_latency'] + $data['get_latency'];		
	}

	if (isset($data['settings'])) {
		$totals[$host]['settings'] = $data['settings'];
		$connections = $connections + ($totals[$host]['settings']['multiplier'] * count($totals[$host]['settings']['memcache']));
	}

	$current_time = microtime(TRUE);
	$elapsed_time = $current_time - $last_write;
	if ($elapsed_time >= $settings['poll']) {
		$last_write = $current_time;
		output_data($elapsed_time);
		reset_samples();
	}
}

function reset_samples() {
/*	reset counters
*/
	global $totals;
	global $connections;

	$connections = 0;
	foreach ($totals as $client_id=>$total) {
		if ($total['disconnected'] != TRUE) {
			if (isset($total['sample'])) {
				foreach ($total['sample'] as $key=>$value) {
					$totals[$client_id]['sample'][$key] = 0;
				}
			}
		}
	}
}

/* MAIN */ 

$in_service = FALSE;
$clients = 0;
$totals = array();
$settings = array();
$hosts = array();
$foutput = NULL;
$xml = NULL;

$options = getopt('o:vi:c:F:');

parse_collector($options, 'c');
parse_verbose($options, 'v');
parse_poll($options, 'i');
parse_output($options, 'o');
parse_output_format($options, 'F');

if ($settings['output_format'] == 'json' && $settings['filename'] != NULL) { 
	include('Services/JSON.php');
}

/* Start collector on requested ip port */
pcntl_signal(SIGINT, "onShutdown", FALSE);
start_server($settings['collector']['server'], $settings['collector']['port']);
?>
